package epoch.game.core.distr;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.rmi.RemoteException;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import epoch.game.core.context.Context;
import epoch.game.core.distr.config.NodeConfig;
import epoch.game.core.message.SysMessageIds;
import epoch.game.core.message.WrappedMessage;
import epoch.game.core.server.ISession;

/**
 * 远程服务节点
 * 
 * @author will
 * @date 2015年11月19日 上午11:13:28
 */
public class RemoteNode {

	public static final Logger log = LogManager.getLogger(RemoteNode.class);

	public RemoteNode(String host, int port) {
		this.host = host;
		this.port = port;
		
	}
	// id
	private int id;
	// name
	private String name;
	// ip地址
	private String host;
	// 端口
	private int port;
	// 所属主节点
	private Node localNode;
	// 连接器
//	private NodeConnector connector;
	// 断开后是否需要重连
	private boolean needRetry=false;
	//重连间隔(s)
	private int retryInterval=30;
	//失败之后尝试次数
	private int retryTimes=10;
	//类型
	NodeType type;
	//连接会话
	ISession session;
	//是否已注册该节点
	private boolean registered;
	//断开连接的时间
	public long disconnectTime;
	
	private long lastSendTime = 0;
	
	public volatile boolean isConnecting = false;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
	
	public int getServerId() {
		return NodeConfig.serverId(id);
	}
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getTag() {
		return String.format("[%d %s:%d] %s", id, host, port, name);
	}
	
	public Node getLocalNode() {
		return localNode;
	}

	public void setLocalNode(Node node) {
		this.localNode = node;
	}
	
	public void setSession(ISession session){
		ISession old = this.session;
		if (old != null) {
			Node.logger.error("RemoteNode change session:{} -> {}", old.remoteIP(), session.remoteIP());
			delSession(old);
			this.disconnectTime = 0;
			old.close();
		}
		this.session = session;
	}
	
	public void delSession(ISession session){
		if (session == null) {
			return;
		}
		if (this.session != session) {
			return;
		}
		this.session = null;
		this.disconnectTime = System.currentTimeMillis();
		
		setRegistered(false);
	}

	public void connect() {
		try {
			localNode.connector.connect(this, 0);
		}catch (Exception e) {
			Node.logger.error("远程节点连接失败：node="+this, e);
		}	
	}

	public boolean isConnected() {
		return session != null && session.isConnected();
	}	
	
	public boolean isUsable(){
		return isConnected() && registered;
	}
	
	public boolean isNeedRetry() {
		return this.needRetry;
	}

	public void setNeedRetry(boolean needRetry) {
		this.needRetry = needRetry;
	}

	public int getRetryInterval() {
		return retryInterval;
	}

	public void setRetryInterval(int retryInterval) {
		this.retryInterval = retryInterval;
	}

	public int getRetryTimes() {
		return retryTimes;
	}

	public void setRetryTimes(int retryTimes) {
		this.retryTimes = retryTimes;
	}
	
	public NodeType getType() {
		return type;
	}
	
	public void setType(NodeType type){
		this.type = type;
	}

	public boolean isRegistered() {
		return registered;
	}

	public void setRegistered(boolean registered) {
		this.registered = registered;
		
		if (registered) {
			log.info("Node registered: {}", getTag());
			localNode.onNodeRegister(this);
		} else {
			log.info("Node unregistered: {}", getTag());
			localNode.onNodeUnregister(this);
		}
	}

	@Override
	public String toString(){
		return "id="+id+",name="+name+",type="+type+",host="+host+",port="+port+",localNodeName="+localNode.getName();
	}

	/**
	 * 发送消息
	 * @param message
	 */
	public void send(Object message) throws IOException{
		if (session==null) {
			throw new RemoteException("发送节点消息失败，未建立远程连接：toNodeId=" + id+",nodeName="+name+",msg="+message);
		}
		if(message instanceof NodeMessage){
			NodeMessage msg = (NodeMessage)message;
			sendNodeMsg(session, msg);
		}else if (message instanceof WrappedMessage) {
			transportMsg(session, (WrappedMessage)message);
		}else{
			log.error("invalid message type");
			//session.write(message);
		}
	}
	
	public static void sendNodeMsg(ISession session, NodeMessage msg) {
		short msgId = msg.getId();
		byte[] data = msg.getMsgData();
		int size = data.length + 2;
		ByteBuffer buffer = ByteBuffer.allocate(size + 4);
		buffer.putInt(size);
		buffer.putShort(msgId);
		buffer.put(data);
		
		byte[] bytes = buffer.array();
		session.writeToSendBuffer(bytes, false);
	}
	
	public static void transportMsg(ISession session, WrappedMessage msg) {
		List<Long> recievers = msg.getReceivers();
		short msgId = msg.getId();
		byte[] data = msg.getMsgData();
		//消息id(2)+接收玩家个数(2)+接收玩家个数*8+消息长度
		int size = 4 + recievers.size()*8 + data.length;
		ByteBuffer buffer = ByteBuffer.allocate(size+4);
		buffer.putInt(size);
		buffer.putShort(msgId);
		buffer.putShort((short) recievers.size());
		for (long recieverId : msg.getReceivers()) {
			buffer.putLong(recieverId);
		}
		buffer.put(data);
		
		byte[] bytes = buffer.array();
		session.writeToSendBuffer(bytes, false);
	}
	
	public void ping() {
		long now = Context.now();
		if (now - lastSendTime >= 10*1000) {
			int size = 2;
			ByteBuffer buffer = ByteBuffer.allocate(size + 4);
			buffer.putInt(size);
			buffer.putShort(SysMessageIds.ping);
			
			byte[] bytes = buffer.array();
			session.writeToSendBuffer(bytes, false);
			
			lastSendTime = now;
		}
	}
	
	public void flushSendMsg(){
		session.flushSendBuffer();
	}
}
